﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using Xceed.Silverlight.Data;

namespace Xceed.Silverlight.ListBox
{
  internal class EnumeratorHelper : DependencyObject
  {
    #region DataEnumerator Property

    internal DataSourceEnumeratorWrapper DataEnumerator
    {
      get
      {
        return m_dataSourceEnumerator;
      }
      set
      {
        if( m_dataSourceEnumerator != value )
        {
          m_dataSourceEnumerator = value;
          this.Initialize();
        }
      }
    }

    #endregion

    #region CurrentDataPath Property

    internal DataPath CurrentDataPath
    {
      get
      {
        return m_currentDataPath;
      }
      set
      {
        if( m_currentDataPath != value )
        {
          m_currentDataPath = value;
        }
      }
    }

    #endregion

    #region CurrentIndexProperty

    internal long CurrentIndex
    {
      get
      {
        return m_currentIndex;
      }
    }

    #endregion

    #region Internal Methods

    internal void Initialize()
    {
      this.Move( 1 );
    }

    internal bool JumpMoveAndFetch( DataPath targetPath, long moveCount, int fetchCount )
    {
      ICollection<DataPath> fetchedItems;

      if( m_dataSourceEnumerator == null )
        return false;

      // Jump operation
      bool retVal = m_dataSourceEnumerator.JumpMoveAndFetch( targetPath, moveCount, fetchCount, out fetchedItems );

      if( retVal )
      {
        // If the operation succeded, update the 
        // currentDataPath and the currentIndex
        m_currentDataPath = fetchedItems.First();
        m_currentIndex = m_dataSourceEnumerator.Index;
        this.OnMoveCompleted();
      }

      return retVal;
    }

    internal bool JumpMoveAndFetch( DataPath targetPath, long moveCount )
    {
      return this.JumpMoveAndFetch( targetPath, moveCount, 1 );
    }

    internal bool JumpMoveAndFetch( DataPath targetPath )
    {
      return this.JumpMoveAndFetch( targetPath, 0 );
    }

    // Method used to keep track of the current DataPath and current index
    // of the DataSourceEnumerator to be able to correctly update the offset
    // of the ScrollBar
    internal bool Move( long moveCount )
    {
      return this.Move( moveCount, false );
    }

    internal bool Move( long moveCount, bool areSkippedItemsLayouted )
    {
      if( m_dataSourceEnumerator == null )
        return false;

      long targetIndex = m_currentIndex + moveCount;
      int fetchCount = ( Math.Sign( moveCount ) >= 0 ) ? 1 : -1;

#if !SILVERLIGHT
      targetIndex = ( targetIndex >= -1 ) ? targetIndex : -1;
#endif

      // Since the items that are about to be skipped are still of some interest,
      // we shouldn't move to far away if we want some sort of caching to occur.
      if( areSkippedItemsLayouted )
      {
        if( Math.Abs( moveCount ) > 1 )
        {
          if( ( moveCount < Int32.MinValue ) || ( moveCount > Int32.MaxValue ) )
            throw new ArgumentOutOfRangeException( "moveCount", "moveCount must be greater than or equal to Int32.MinValue and less than or equal to Int32.MaxValue." );

          fetchCount = ( int )moveCount;
          moveCount = Math.Sign( moveCount );
        }
      }

      ICollection<DataPath> fetchedItems;
      bool retval = m_dataSourceEnumerator.MoveAndFetch( moveCount, fetchCount, out fetchedItems );

      if( ( fetchedItems != null ) && ( fetchedItems.Count > 0 ) )
      {
        m_currentDataPath = fetchedItems.Last();
      }

      m_currentIndex = m_dataSourceEnumerator.Index;

      if( m_currentIndex != targetIndex )
      {
        this.OnMoveCompleted();
      }

      return retval;
    }

    internal void InvalidateCurrentDataPath()
    {
      m_currentDataPath = null;
    }

    internal void EnsurePositionFromSourcePosition( SourcePosition sourcePosition )
    {
      // No need to ensure the position
      if( this.m_dataSourceEnumerator.ItemCount == 0 )
        return;

      if( sourcePosition.IsAfterLast )
      {
        if( !this.Move( -1 ) )
          throw new ListBoxInternalException( "Cannot move back." );
      }
      else if( sourcePosition.IsBeforeFirst )
      {
        if( !this.Move( 1 ) )
          throw new ListBoxInternalException( "Cannot move forward." );
      }
    }

    internal void EnsureDataSourceEnumeratorPosition()
    {
      long index = m_dataSourceEnumerator.Index;

      if( m_dataSourceEnumerator.ItemCount == index )
      {
        this.Move( -1 );
      }
      else if( index == -1 )
      {
        // We must reset local fields in order to 
        // avoid Index and CurrentDataPath inconsistency
        m_currentDataPath = null;
        m_currentIndex = -1;
        this.Move( 1 );
      }
      else
      {
        this.Move( 0 );
      }
    }

    internal void RefreshCurrentDataPath()
    {
      if( this.CurrentDataPath != null )
        return;

      if( !this.Move( 1 ) )
      {
        if( !this.Move( -1 ) )
        {
          if( m_dataSourceEnumerator.ItemCount != 0 )
            throw new ListBoxInternalException( "Unable to position the DataSourceEnumerator to obtain current in a source where the ItemCount is greater than 0." );
        }
      }
    }

    #endregion

    #region Private Methods

    private void OnMoveCompleted()
    {
      if( this.MoveCompleted != null )
      {
        this.MoveCompleted( this, new MoveCompletedEventArgs( m_currentIndex ) );
      }
    }

    #endregion

    #region Events

    internal event EventHandler<MoveCompletedEventArgs> MoveCompleted;

    #endregion

    #region Private Members

    private DataPath m_currentDataPath;
    private long m_currentIndex;

    private DataSourceEnumeratorWrapper m_dataSourceEnumerator;

    #endregion
  }
}
